package com.gitee.fantaros.web.fan.netcut.engine;

import com.oracle.truffle.js.scriptengine.GraalJSScriptEngine;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import java.io.FileReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Properties;

@SuppressWarnings("unused")
@Component
public class FanStartEngine {
    private static final String ENGINE_PATH_CONFIG_NAME = "service.fanstart.engine.path";
    private static final String ENGINE_PATH_DEFAULT_CONFIG_NAME = "../conf";

    private static final String MAIN_SCRIPT_NAME = "main.js";
    public static final String SCRIPT_PATH = "assets/scripts/";
    public static final String IMAGE_PATH = "assets/images/";
    private static final Logger LOG = LoggerFactory.getLogger(FanStartEngine.class);

    private String lastErrorMessage;
    private final ScriptEngine scriptEngine;
    private final String enginePath;
    @Getter
    private String scriptPath;

    private final Environment environment;

    public String getConfig(String name, String defaultValue) {
        return environment.getProperty(name, defaultValue);
    }

    public FanStartEngine(ApplicationContext applicationContext, Environment environment) {
        this.environment = environment;
        this.enginePath = getConfig(ENGINE_PATH_CONFIG_NAME, ENGINE_PATH_DEFAULT_CONFIG_NAME);
        this.scriptPath = String.format("%1$s/%2$s", this.enginePath, SCRIPT_PATH);
        Properties sysConfig = System.getProperties();
        this.scriptEngine = GraalJSScriptEngine.create();
        Bindings bindings = this.scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
        this.scriptEngine.put("spring", applicationContext);
        this.scriptEngine.put("senv", this.environment);
        this.scriptEngine.put("engine", this);
        this.scriptEngine.put("sbinding", bindings);
        this.scriptEngine.put("epath", enginePath);
        this.scriptEngine.put("sysConfig", sysConfig);
        this.scriptEngine.put("sout", System.out);
        this.scriptEngine.put("sin", System.in);
        this.scriptEngine.put("serr", System.err);
        this.scriptEngine.put("slog", LOG);
    }

    public Object execute() {
        String mainScriptPath = MAIN_SCRIPT_NAME;
        if (this.enginePath != null) {
            mainScriptPath = String.format("%1$s/%2$s", this.enginePath, MAIN_SCRIPT_NAME);
        }
        try {
            this.scriptEngine.eval("load('" + mainScriptPath + "');");
            Invocable invocable = (Invocable) this.scriptEngine;
            return invocable.invokeFunction("main");
        } catch (Exception e) {
            LOG.warn("main.js execute failed", e);
            return null;
        }
    }

    public void requirejs(String scriptFilePrefix) {
        loadLib(this.enginePath.concat("/assets/scripts/").concat(scriptFilePrefix).concat(".js"));
    }

    public void require(String scriptFileName) {
        loadLib(this.enginePath.concat("/assets/scripts/").concat(scriptFileName));
    }

    public void loadLib(String scriptPath) {
        Reader scriptReader = null;
        try {
            scriptReader = new FileReader(scriptPath, StandardCharsets.UTF_8);
        } catch (Exception e) {
            LOG.warn("load script lib failed", e);
        }
        if (scriptReader == null) {
            LOG.warn("lib {}加载失败", scriptPath);
        } else {
            runScriptReader(scriptPath, scriptReader);
        }
    }

    protected void runScriptReader(String scriptPath, Reader scriptReader) {
        if (scriptPath == null || scriptReader == null) {
            LOG.warn("[runScriptReader]没有加载到任何脚本，请传入正确的脚本信息");
            return;
        }
        try {
            this.scriptEngine.eval(scriptReader);
            LOG.info("脚本文件{}加载成功", scriptPath);
        } catch (Exception e) {
            LOG.warn("run & read script lib failed", e);
            LOG.warn("脚本文件{}加载失败", scriptPath);
        }

    }

}
